package com.webcamtracker.ui;


import com.webcamtracker.ms2.ColorHistogramIndex;
import com.webcamtracker.ms2.ModelParameters;
import com.webcamtracker.ms2.ModelParametersFactoryImpl;
import com.webcamtracker.ms2.TrackingFilter;
import com.webcamtracker.ui.utils.WindowUtilities;
import com.webcamtracker.video.VideoSuscriptor;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import java.util.logging.Level;
import java.util.logging.Logger;

/*
working image
pick target model
pick initial position
track target

kernel
histogram

*/
public class UITrackingImageProcessor extends JPanel implements VideoSuscriptor {
    private UIDrawableCanvas outputUIImageCanvas;
    private UIDrawableCanvas targetModelUIImageCanvas;
    private UIColorHistogramIndex targetModelUIColorHistogramIndex;
    private UIQuantizedImage targetModelUIQuantizedImage;
    private UIModelParameters targetModelUIParameters;
    private JPanel controlsJPanel;

    final static int STATE_PICK_INITIAL_POSITION = 0;
    final static int STATE_PICK_TARGET_MODEL = 1;
    final static int STATE_SELECT_BEHAVIOR = 2;
    final static int STATE_TRACKING = 3;
    final static int STATE_PICK_FRAME = 4;

    private Logger logger = Logger.getLogger(UITrackingImageProcessor.class.getName());
    private UITrackingImageProcessorBehavior[] uiTrackingImageProcessorBehaviors;
    private int uiState = -1;
    private AbstractAction setFrameAction;
    private AbstractAction setInitialPositionAction;
    private AbstractAction setTargetModelAction;
    private AbstractAction trackTargetAction;
    private BufferedImage workingImage;

    private Point initialPosition;
    private BufferedImage targetModelImage;

    public void arrivedFrame(BufferedImage image) {

        getBehavior().process(image);
    }

    static interface UITrackingImageProcessorBehavior {
        int getState();

        int doPickFrame();

        int doPickInitialPosition();

        int doPickTargetModel();

        int doTrackTarget();

        void attachBehavior();

        void dettachBehavior();

        void process(BufferedImage image);
    }

    class BaseBehavior implements UITrackingImageProcessorBehavior {
        private int uiState;

        public BaseBehavior(int uiState) {
            this.uiState = uiState;
        }

        public int getState() {
            return uiState;
        }

        public void attachBehavior() {
        }

        public void dettachBehavior() {
        }

        public int doPickInitialPosition() {
            return -1;
        }

        public int doPickTargetModel() {
            return -1;
        }

        public void process(BufferedImage image) {
        }

        public int doPickFrame() {
            return -1;
        }

        public int doTrackTarget() {
            return -1;
        }
    }

    private class PickInitialPositionBehavior extends BaseBehavior {
        private MouseListener mouseListener;

        public PickInitialPositionBehavior() {
            super(STATE_PICK_INITIAL_POSITION);
            mouseListener = new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    initialPosition = e.getPoint();
                    outputUIImageCanvas.repaint();
                }
            };
        }

        public void attachBehavior() {
            super.attachBehavior();
            setInitialPositionAction.setEnabled(true);
            outputUIImageCanvas.addMouseListener(mouseListener);
        }

        public void dettachBehavior() {
            super.dettachBehavior();
            outputUIImageCanvas.removeMouseListener(mouseListener);
            setInitialPositionAction.setEnabled(false);
        }

        public int doPickInitialPosition() {
            outputUIImageCanvas.repaint();
            return STATE_SELECT_BEHAVIOR;
        }
    }


    private class PickFrameBehavior extends BaseBehavior {
        public PickFrameBehavior() {
            super(STATE_PICK_FRAME);
        }

        private void reset() {
            initialPosition = null;
        }

        public void attachBehavior() {
            super.attachBehavior();
            setFrameAction.setEnabled(true);
            reset();
            outputUIImageCanvas.repaint();
        }

        public void dettachBehavior() {
            super.dettachBehavior();
            setFrameAction.setEnabled(false);
        }

        public int doPickFrame() {
            return STATE_SELECT_BEHAVIOR;
        }

        public void process(BufferedImage image) {
            workingImage = image;
            outputUIImageCanvas.repaint();
        }
    }


    private class PickTargetModelBehavior extends BaseBehavior {
        private MouseListener mouseListener;
        private int counter;
        private Point targetModelULPoint;
        private Point targetModelBRPoint;
        private UIDrawableCanvas.Drawable targetmodelDrawable;

        public PickTargetModelBehavior() {
            super(STATE_PICK_TARGET_MODEL);
            mouseListener = new MouseAdapter() {
                public void mouseClicked(MouseEvent e) {
                    counter = (counter + 1) % 2;
                    if (counter % 2 == 0) {
                        targetModelULPoint = e.getPoint();
                    } else {
                        targetModelBRPoint = e.getPoint();
                    }
                    outputUIImageCanvas.repaint();
                }
            };

            targetmodelDrawable = new UIDrawableCanvas.Drawable() {
                public void paint(Graphics graphics) {
                    Color color = graphics.getColor();
                    if (targetModelBRPoint != null && targetModelULPoint != null) {
                        graphics.setColor(Color.YELLOW);
                        graphics.drawRect((int) targetModelULPoint.getX(), (int) targetModelULPoint.getY(), (int) (targetModelBRPoint.getX() - targetModelULPoint.getX()), (int) (targetModelBRPoint.getY() - targetModelULPoint.getY()));
                    }
                    graphics.setColor(Color.GREEN);
                    if (targetModelBRPoint != null) {
                        graphics.fillOval((int) targetModelBRPoint.getX(), (int) targetModelBRPoint.getY(), 5, 5);
                    }
                    if (targetModelULPoint != null) {
                        graphics.fillOval((int) targetModelULPoint.getX(), (int) targetModelULPoint.getY(), 5, 5);
                    }
                    graphics.setColor(color);
                }
            };
        }

        private void reset() {
            counter = -1;
            targetModelULPoint = null;
            targetModelBRPoint = null;
        }

        public void attachBehavior() {
            super.attachBehavior();
            reset();
            setTargetModelAction.setEnabled(true);
            outputUIImageCanvas.addMouseListener(mouseListener);
            outputUIImageCanvas.add(targetmodelDrawable);
        }

        public void dettachBehavior() {
            super.dettachBehavior();
            outputUIImageCanvas.remove(targetmodelDrawable);
            outputUIImageCanvas.removeMouseListener(mouseListener);
            setTargetModelAction.setEnabled(false);
        }

        public int doPickTargetModel() {
            if (targetModelULPoint != null && targetModelBRPoint != null) {

                if (((targetModelBRPoint.x - targetModelULPoint.x) % 2) != 0) {
                    targetModelBRPoint.x++;
                }
                if (((targetModelBRPoint.y - targetModelULPoint.y) % 2) != 0) {
                    targetModelBRPoint.y++;
                }
                targetModelImage = workingImage.getSubimage(targetModelULPoint.x, targetModelULPoint.y, targetModelBRPoint.x - targetModelULPoint.x, targetModelBRPoint.y - targetModelULPoint.y);
            }

            ColorHistogramIndex targetModelColorHistogramIndex = new ColorHistogramIndex(targetModelImage.getWidth(), targetModelImage.getHeight(), TrackingFilter.NUMBEROFBINS);
            TrackingFilter.captureRGBHistogram(targetModelImage, targetModelColorHistogramIndex, new Point(targetModelImage.getWidth() / 2, targetModelImage.getHeight() / 2), targetModelImage.getWidth(), targetModelImage.getHeight());
            targetModelUIColorHistogramIndex.setColorHistogramIndex(targetModelColorHistogramIndex);
            targetModelUIQuantizedImage.setColorHistogramIndex(targetModelColorHistogramIndex);

            ModelParameters modelParameters = ModelParametersFactoryImpl.getInstance().createModelParameters(targetModelImage.getHeight() / 2, targetModelImage.getWidth() / 2);
            targetModelUIParameters.setModelParameters(modelParameters);

            targetModelUIImageCanvas.repaint();
            outputUIImageCanvas.repaint();
            targetModelUIColorHistogramIndex.repaint();
            targetModelUIQuantizedImage.repaint();
            targetModelUIParameters.repaint();
            return STATE_SELECT_BEHAVIOR;
        }
    }


    private class SelectBehavior extends BaseBehavior {
        public SelectBehavior() {
            super(STATE_SELECT_BEHAVIOR);
        }

        public int doPickInitialPosition() {
            return STATE_PICK_INITIAL_POSITION;
        }

        public int doPickTargetModel() {
            return STATE_PICK_TARGET_MODEL;
        }

        public int doTrackTarget() {
            return STATE_TRACKING;
        }

        public int doPickFrame() {
            return STATE_PICK_FRAME;
        }

        public void attachBehavior() {
            super.attachBehavior();
            setFrameAction.setEnabled(true);
            setInitialPositionAction.setEnabled(workingImage != null);
            setTargetModelAction.setEnabled(workingImage != null);
            trackTargetAction.setEnabled(isValidConfiguration());
        }

        public void dettachBehavior() {
            super.dettachBehavior();
            setFrameAction.setEnabled(false);
            setInitialPositionAction.setEnabled(false);
            setTargetModelAction.setEnabled(false);
            trackTargetAction.setEnabled(false);
        }
    }


    private class TrackingBehavior extends BaseBehavior {
        private volatile boolean finished;

        public void attachBehavior() {
            super.attachBehavior();
            finished = false;
            trackTargetAction.setEnabled(true);
            trackTargetAction.putValue(Action.NAME, "Stop Tracking");
            new Thread(new Runnable() {
                public void run() {
                    TrackingFilter trackingFilter = new TrackingFilter();
                    trackingFilter.setTargetModel(UITrackingImageProcessor.this.targetModelImage);
                    while (!finished) {
                        trackingFilter.meanShiftTracking(UITrackingImageProcessor.this.workingImage, UITrackingImageProcessor.this.initialPosition);
                        initialPosition = trackingFilter.currentBoxCOG;
                        SwingUtilities.invokeLater(new Runnable() {
                            public void run() {
                                outputUIImageCanvas.repaint();
                            }
                        });
                    }
                }
            }).start();
        }

        public void dettachBehavior() {
            super.dettachBehavior();
            trackTargetAction.setEnabled(false);
            trackTargetAction.putValue(Action.NAME, "Start Tracking");
        }

        public TrackingBehavior() {
            super(STATE_TRACKING);
        }

        public int doTrackTarget() {
            finished = true;
            return STATE_SELECT_BEHAVIOR;
        }

        public void process(BufferedImage image) {
            workingImage = image;
            outputUIImageCanvas.repaint();
        }
    }


    public UITrackingImageProcessor() {
        super(true);

        configure();
        configureUI();
        init();
    }

    public String getName() {
        return "Tracking Image Processor";
    }

    private boolean isValidConfiguration() {
        return workingImage != null && initialPosition != null && targetModelImage != null;
    }

    private void configure() {
        logger.setLevel(Level.INFO);
        outputUIImageCanvas = new UIDrawableCanvas(new Dimension(320, 240));
        outputUIImageCanvas.add(new UIDrawableCanvas.Drawable() {
            public void paint(Graphics graphics) {
                Color oldColor = graphics.getColor();
                if (workingImage != null) {
                    graphics.drawImage(workingImage, 0, 0, null);
                }
                if (initialPosition != null) {
                    graphics.setColor(Color.RED);
                    graphics.fillOval((int) initialPosition.getX(), (int) initialPosition.getY(), 5, 5);
                    if (targetModelImage != null) {
                        graphics.setColor(Color.GREEN);
                        int halfWidth = targetModelImage.getWidth() / 2;
                        int halfHeight = targetModelImage.getHeight() / 2;
                        graphics.drawRect(initialPosition.x - halfWidth, initialPosition.y - halfHeight, targetModelImage.getWidth(), targetModelImage.getHeight());
                    }
                }
                graphics.setColor(oldColor);
            }
        });
        targetModelUIImageCanvas = new UIDrawableCanvas(outputUIImageCanvas.getPreferredSize());
        targetModelUIImageCanvas.add(new UIDrawableCanvas.Drawable() {
            public void paint(Graphics graphics) {
                if (targetModelImage != null) {
                    graphics.drawImage(targetModelImage, 0, 0, null);
                }
            }
        });
        targetModelUIColorHistogramIndex = new UIColorHistogramIndex();
        targetModelUIQuantizedImage = new UIQuantizedImage();

        targetModelUIParameters = new UIModelParameters();

        createControlsPanel();
        createBehaviors();
    }

    private void createBehaviors() {
        uiTrackingImageProcessorBehaviors = new UITrackingImageProcessorBehavior[5];
        uiTrackingImageProcessorBehaviors[STATE_PICK_INITIAL_POSITION] = new PickInitialPositionBehavior();
        uiTrackingImageProcessorBehaviors[STATE_PICK_TARGET_MODEL] = new PickTargetModelBehavior();
        uiTrackingImageProcessorBehaviors[STATE_SELECT_BEHAVIOR] = new SelectBehavior();
        uiTrackingImageProcessorBehaviors[STATE_TRACKING] = new TrackingBehavior();
        uiTrackingImageProcessorBehaviors[STATE_PICK_FRAME] = new PickFrameBehavior();
        setBehavior(STATE_SELECT_BEHAVIOR);
    }

    private void createControlsPanel() {
        controlsJPanel = new JPanel();
        controlsJPanel.setLayout(new BoxLayout(controlsJPanel, BoxLayout.Y_AXIS));

        setFrameAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                doPickFrame();
            }
        };
        setFrameAction.putValue(Action.NAME, "Pick Frame");
        controlsJPanel.add(new JButton(setFrameAction));

        controlsJPanel.add(Box.createRigidArea(new Dimension(0, 10)));

        setInitialPositionAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                doPickInitialPosition();
            }
        };
        setInitialPositionAction.putValue(Action.NAME, "Pick Initial Position");
        controlsJPanel.add(new JButton(setInitialPositionAction));

        controlsJPanel.add(Box.createRigidArea(new Dimension(0, 10)));

        setTargetModelAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                doPickTargetModel();
            }
        };
        setTargetModelAction.putValue(Action.NAME, "Pick Target Model");
        controlsJPanel.add(new JButton(setTargetModelAction));

        controlsJPanel.add(Box.createRigidArea(new Dimension(0, 10)));

        trackTargetAction = new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                doTrackTarget();
            }
        };
        trackTargetAction.putValue(Action.NAME, "Start Tracking");
        controlsJPanel.add(new JButton(trackTargetAction));
    }

    private void setBehavior(int newUIState) {
        if (newUIState != -1 && newUIState != uiState) {
            if (uiState != -1) {
                getBehavior().dettachBehavior();
            }
            uiState = newUIState;
            getBehavior().attachBehavior();
        }
    }

    private UITrackingImageProcessorBehavior getBehavior() {
        return uiTrackingImageProcessorBehaviors[uiState];
    }

    private void doPickFrame() {
        setBehavior(getBehavior().doPickFrame());
    }

    private void doPickInitialPosition() {
        setBehavior(getBehavior().doPickInitialPosition());
    }

    private void doPickTargetModel() {
        setBehavior(getBehavior().doPickTargetModel());
    }

    private void doTrackTarget() {
        setBehavior(getBehavior().doTrackTarget());
    }

    private void configureUI() {
        setBorder(BorderFactory.createTitledBorder(getName()));
        setLayout(new BorderLayout());
        //setLayout(new FormLayout("",""));

        JComponent WorkingImageComponent = WindowUtilities.titledComponent("Working Image", outputUIImageCanvas);
        JComponent controlsComponent = WindowUtilities.titledComponent("Controls", controlsJPanel);

        JTabbedPane jTabbedPane = new JTabbedPane();
        jTabbedPane.addTab("Target Model", targetModelUIImageCanvas);
        jTabbedPane.addTab("Target Model Histogram", targetModelUIColorHistogramIndex);
        jTabbedPane.addTab("Target Model Quantized", targetModelUIQuantizedImage);
        jTabbedPane.addTab("Target Model Parameters", targetModelUIParameters);

        JPanel innerPanel = new JPanel(new BorderLayout());
        innerPanel.add(WorkingImageComponent, BorderLayout.NORTH);
        innerPanel.add(jTabbedPane, BorderLayout.SOUTH);
        JPanel outerPanel = new JPanel(new BorderLayout());
        outerPanel.add(innerPanel, BorderLayout.EAST);
        outerPanel.add(controlsComponent, BorderLayout.WEST);
        add(outerPanel, BorderLayout.CENTER);
    }

    private void init() {
    }

}
